import ipaddress
from urllib import parse as urllib_parse

from django.db.models import Q
from authlib.integrations.django_client import OAuth, DjangoOAuth2App

from apps.app_aai_proxy.models import SysProxyApp
from apps.app_aai_proxy.managers import aai_proxy_configs


def is_domain_match(host: str, pattern: str) -> bool:
    """
    判断 host 是否匹配 pattern（支持 *.example.com 这种通配符）
    """
    # 精确匹配
    if host.startswith('.'):
        return False

    if host == pattern:
        return True

    # 通配符匹配 *.example.com -> a.example.com, a.b.example.com ...
    if pattern.startswith("*."):
        # 分割主域部分
        pattern_domain = pattern[2:]
        pattern_parts = pattern_domain.split(".")
        host_parts = host.split(".")

        # host域名层级 要比 主域名层级多
        domaim_len = len(pattern_parts)
        if len(host_parts) <= domaim_len:
            return False

        # 主域是否一致
        return ".".join(host_parts[-domaim_len:]) == pattern_domain

    return False


def is_ip_match(ip: str, cidr: str) -> bool:
    """
    判断 ip 是否属于 cidr 表示的网络范围
    cidr: 192.168.1.0/24
    """
    try:
        ip_obj = ipaddress.ip_address(ip)
        network = ipaddress.ip_network(cidr, strict=False)
        return ip_obj in network
    except ValueError:
        return False


def is_host_match(host: str, pattern: str) -> bool:
    """
    自动判断 host 是否匹配 pattern。
    支持：
      - 域名：*.example.com
      - IP地址：192.168.1.0/24
    """
    # 如果 pattern 是 CIDR 格式，则当作 IP 处理
    if "/" in pattern:
        return is_ip_match(host, pattern)

    # 否则当作域名处理
    return is_domain_match(host, pattern)


class AAIProxyHelper:
    @staticmethod
    def replace_query_params(url, params: dict):
        """
        替换或添加url中query参数的值
        :param url: url
        :param params: 键值对参数
        :return:
            new url; type str
        """
        (scheme, netloc, path, query, fragment) = urllib_parse.urlsplit(url)
        query_dict = urllib_parse.parse_qs(query, keep_blank_values=True)
        for key, val in params.items():
            query_dict[key] = [val]

        query = urllib_parse.urlencode(sorted(list(query_dict.items())), doseq=True)
        return urllib_parse.urlunsplit((scheme, netloc, path, query, fragment))

    @staticmethod
    def get_aai_proxy_url() -> str:
        base_url: str = aai_proxy_configs.get(aai_proxy_configs.ConfigName.PROXY_BASE_URL.value)
        base_url = base_url.rstrip('/ ')
        return base_url + "/oidc/aai/redir2Authentication"

    @staticmethod
    def get_aai_proxy_callback_url():
        proxy_base_url = aai_proxy_configs.get(aai_proxy_configs.ConfigName.PROXY_BASE_URL)
        return urllib_parse.urljoin(base=proxy_base_url, url='/oidc/aai/redirectCode2App')

    @staticmethod
    def allow_client(url) -> bool:
        try:
            parse_obj = urllib_parse.urlparse(url)
        except ValueError:
            return False

        host = parse_obj.hostname
        if host and host.endswith('cstcloud.cn'):
            return True
        elif SysProxyApp.objects.filter(url=host, state=True).exists():
            return True
        else:
            # 通配符格式匹配
            objs = SysProxyApp.objects.filter(Q(url__icontains='*') | Q(url__icontains='/'), state=True).all()
            for obj in objs:
                try:
                    ok = is_host_match(host=host, pattern=obj.url)
                    if ok:
                        return True
                except Exception:
                    continue

        return False

    @staticmethod
    def build_aai_logout_url(logout_url: str) -> str:
        aai_base_url = aai_proxy_configs.get(aai_proxy_configs.ConfigName.AAI_BASE_URL.value)
        logout_back_url = aai_proxy_configs.get(aai_proxy_configs.ConfigName.AAI_LOGOUT_BACK_URL.value)
        aai_base_url = aai_base_url.rstrip('/ ')
        logout_back_url = logout_back_url.rstrip('/? ')
        web_query = urllib_parse.urlencode(query={'state': logout_url})
        web_server_url = f'{logout_back_url}?{web_query}'
        query =urllib_parse.urlencode(query={'WebServerURL': web_server_url})
        return f'{aai_base_url}/oidc/userInfo/logout?{query}'


class OIDCClient:
    @classmethod
    def build_client(cls):
        return OIDCClient(
            base_url=aai_proxy_configs.get(aai_proxy_configs.ConfigName.AAI_BASE_URL.value),
            meta_url=aai_proxy_configs.get(aai_proxy_configs.ConfigName.AAI_META_URL.value),
            client_id=aai_proxy_configs.get(aai_proxy_configs.ConfigName.AAI_CLIENT_ID.value),
            client_secret=aai_proxy_configs.get(aai_proxy_configs.ConfigName.AAI_CLIENT_SECRET.value)
        )

    def __init__(self, base_url: str, meta_url: str, client_id: int, client_secret: str):
        self.aai_base_url = base_url
        self.aai_client_id = client_id
        self.aai_client_secret = client_secret
        self._aai_meta_url = meta_url
        self._aai_oidc_client = None

    def get_aai_oidc_client(self) -> DjangoOAuth2App:
        if self._aai_oidc_client is not None:
            return self._aai_oidc_client

        oauth = OAuth()
        oauth.register(
            'oidc', client_id=self.aai_client_id, client_secret=self.aai_client_secret,
            server_metadata_url=self._get_aai_meta_api(), client_kwargs={'default_timeout': (6, 20)}
        )
        self._aai_oidc_client = oauth.create_client('oidc')
        return self._aai_oidc_client

    def _get_aai_meta_api(self) -> str:
        if self._aai_meta_url.startswith("http"):
            return self._aai_meta_url

        return urllib_parse.urljoin(self.aai_base_url, self._aai_meta_url)

    def get_aai_provider_metadata(self):
        aai_client = self.get_aai_oidc_client()
        return aai_client.load_server_metadata()

    def build_authentication_url(self, redirect_url: str, state: str, nonce: str, scope: str) -> str:
        """
        构建AAI登录url
        """
        aai_client = self.get_aai_oidc_client()
        rv = aai_client.create_authorization_url(redirect_uri=redirect_url, state=state, nonce=nonce, scope=scope)
        return rv['url']

    def code2token(self, code: str) -> dict:
        aai_client = self.get_aai_oidc_client()
        redirect_uri = AAIProxyHelper.get_aai_proxy_callback_url()
        token = aai_client.fetch_access_token(code=code, redirect_uri=redirect_uri)
        return token

    def get_user_info(self, token: dict):
        """
        param token: OAuth2Token(dict)
        """
        aai_client = self.get_aai_oidc_client()
        info = aai_client.userinfo(token=token)
        return info
